/**
 * 
 */
package be.destin.abdera;

import java.security.Principal;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import org.apache.abdera.ext.json.JSONFilter;
import org.apache.abdera.ext.opensearch.OpenSearchConstants;
import org.apache.abdera.ext.opensearch.model.Query;
import org.apache.abdera.ext.opensearch.server.OpenSearchUrlAdapter;
import org.apache.abdera.ext.opensearch.server.impl.SimpleOpenSearchInfo;
import org.apache.abdera.ext.opensearch.server.impl.SimpleOpenSearchQueryInfo;
import org.apache.abdera.ext.opensearch.server.impl.SimpleOpenSearchUrlInfo;
import org.apache.abdera.ext.opensearch.server.impl.SimpleOpenSearchUrlParameterInfo;
import org.apache.abdera.ext.opensearch.server.processors.OpenSearchDescriptionRequestProcessor;
import org.apache.abdera.ext.opensearch.server.processors.OpenSearchTargetTypes;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.RequestProcessor;
import org.apache.abdera.protocol.server.TargetType;
import org.apache.abdera.protocol.server.WorkspaceInfo;
import org.apache.abdera.protocol.server.impl.AbstractWorkspaceProvider;
import org.apache.abdera.protocol.server.impl.RegexTargetResolver;
import org.apache.abdera.protocol.server.impl.TemplateTargetBuilder;
import org.apache.abdera.protocol.server.provider.basic.BasicProvider;
import org.apache.log4j.Logger;


/**
 * Examples of URL supported: (Internet Explorer, Opera and Firefox display them (surprizingly not Chrome))
 *  
 *	http://localhost/scheme/concept.svc : AtomPub service document listing all existing collections
 *	http://localhost/scheme/concept.svc?q=frag : All thesaurus entries beginning with frag
 *  http://localhost/scheme/concept.svc/opensearch.osdx : Install a search engine shortcut in Internet Explorer (TODO: finalize this)
 *	http://localhost/scheme/concept.svc/thPlants  : All plants! Very big. thFood is more reasonable!!!
 *	http://localhost/scheme/concept.svc/thPlants;categories : Categories for plants, NOT IMPLEMENTED
 *	http://localhost/scheme/concept.svc/thPlants?q=fragaria : plants with a name beginning with FRAGARIA
 *	http://localhost/scheme/concept.svc/thPlants(15000753) : Terminology for one plant (not displayed by browsers)
 *	http://localhost/scheme/concept.svc/thPlants/15000753 : idem
 *	http://localhost/scheme/concept.svc/thPlants_15000753 : idem
 *	http://localhost/scheme/concept.svc/thPlants(15000753)/$links/narrower (broader, related, xxxMatch...)
 *
 *  TODO:
 *  http://localhost/scheme/memo.svc/scheme(thPlants_15000753) : all stats, tags and comments about record thPlants_15000753 in application "scheme" 
 *  /$links/application_role or application:role : not implemented yet, should redirect somehow to AtomURL of the application
 * @author Christophe
 *
 */
public abstract class AbstractCorrectedWorkspaceProvider extends AbstractWorkspaceProvider {

	/** log4j category */
	private static Logger log = Logger.getLogger(AbstractCorrectedWorkspaceProvider.class);

    public static final String OPENSEARCH_EXTENSION_NS = "http://schemas.microsoft.com/opensearchext/2009/";
    public static final String OSE_PREFIX = "ose";

    public static final TargetType TYPE_FEED_PATCH = TargetType.get("feed",true);

    public static final String PARAM_SEARCHTERMS = "q";  // $filter is a SQL like boolean expression: no good for text search!
	public static final String PARAM_STARTINDEX = "s";
	public static final String PARAM_COUNT = "c"; // perfect synonym of OData $top
	public static final String PARAM_LANGUAGE = "l"; // perfect synonym of OData $top
	public static final String QUERY_LANGUAGE_LN = "language";

	public static final String PARAM_FILTER = "$filter"; // $ must be escaped for .replace
	public static final String QUERY_FILTER_LN = "filter";
	public static final String PARAM_SKIP = "$skip";// $ must be escaped for .replace
	public static final String QUERY_SKIP_LN = "skip";
	public static final String PARAM_TOP = "$top";// $ must be escaped for .replace
	
	public static final String PARAM_SORT = "$orderby";// $ must be escaped for .replace
	public static final String QUERY_SORT_LN = "order";
	
	public static final String PARAM_LIST = PARAM_SEARCHTERMS+','+PARAM_STARTINDEX+','+PARAM_COUNT+','+PARAM_LANGUAGE
								+','+PARAM_FILTER+','+PARAM_SKIP+','+PARAM_TOP+','+PARAM_SORT;

	protected WorkspaceInfo workspace;

    public String contextPath;


	public static final String PARAM_NAVIGATION = "navigation";
	public static final String ENTRIES_LIST = "ENTRIES_LIST";
	public static final String ENTRY_LINKS = "ENTRY_LINKS";
	public static final TargetType TYPE_ENTRY_LINKS = TargetType.get(ENTRY_LINKS,true);
	public static final TargetType TYPE_ENTRIES_LIST = TargetType.get(ENTRIES_LIST,true);
    
	/**
	 * 
	 */
	public void init(String host, String base, WorkspaceInfo wi) {

		contextPath = base;
    	log.info("Provider base path="+base);

        // The target resolver provides the URL path mappings
		setTargetResolver(      
				new RegexTargetResolver()
				.setPattern('/'+base+"/?", TargetType.TYPE_SERVICE)
				.setPattern('/'+base+"/opensearch\\.osdx(#.*)?", OpenSearchTargetTypes.OPENSEARCH_DESCRIPTION)
				.setPattern('/'+base+"/?\\?[^#]*(#.*)?", OpenSearchTargetTypes.OPENSEARCH_URL)
				.setPattern('/'+base+"/([^./#?;_()]+);categories(#.*)?", TargetType.TYPE_CATEGORIES, BasicProvider.PARAM_FEED)
				.setPattern('/'+base+"/([^./#?;_()]+)\\?[^#]*(#.*)?", OpenSearchTargetTypes.OPENSEARCH_URL, BasicProvider.PARAM_FEED)
				.setPattern('/'+base+"/([^./#?;_()]+)(#.*)?", TargetType.TYPE_COLLECTION, BasicProvider.PARAM_FEED)
				.setPattern('/'+base+"/([^./#?;_()]+)\\(([^,/#?()]+)\\)(#.*)?", TargetType.TYPE_ENTRY, BasicProvider.PARAM_FEED, BasicProvider.PARAM_ENTRY)
				.setPattern('/'+base+"/([^./#?;_()]+)\\(([^/#?()]+)\\)(#.*)?", AbstractCorrectedWorkspaceProvider.TYPE_ENTRIES_LIST, BasicProvider.PARAM_FEED, AbstractCorrectedWorkspaceProvider.ENTRIES_LIST)
				.setPattern('/'+base+"/([^./#?;_()]+)/([^/#?()]+)(#.*)?", TargetType.TYPE_ENTRY, BasicProvider.PARAM_FEED, BasicProvider.PARAM_ENTRY)
				.setPattern('/'+base+"/([^./#?;_()]+)_([^/#?()]+)(#.*)?", TargetType.TYPE_ENTRY, BasicProvider.PARAM_FEED, BasicProvider.PARAM_ENTRY)
				.setPattern('/'+base+"/([^./#?;_()]+)\\(([^/#?()]+)\\)/\\$links/([^/#?()]+)(#.*)?", AbstractCorrectedWorkspaceProvider.TYPE_ENTRY_LINKS, BasicProvider.PARAM_FEED, BasicProvider.PARAM_ENTRY,AbstractCorrectedWorkspaceProvider.PARAM_NAVIGATION)
				.setPattern('/'+base+"/([^./#?;_()]+)\\(([^/#?()]+)\\)/\\$links/([^/#?()]+)(\\?[^#]*)(#.*)?", OpenSearchTargetTypes.OPENSEARCH_URL, BasicProvider.PARAM_FEED, BasicProvider.PARAM_ENTRY,AbstractCorrectedWorkspaceProvider.PARAM_NAVIGATION)
				);

        // The target builder is used to construct url's for the various targets
		setTargetBuilder(
				new TemplateTargetBuilder()
				.setTemplate(TargetType.TYPE_SERVICE, "{target_base}/")
				.setTemplate(TargetType.TYPE_COLLECTION, "{target_base}/{collection}{-opt|?|"+PARAM_LIST+"}{-join|&|"+PARAM_LIST+"}")
				.setTemplate("feed", "{target_base}/{collection}{-opt|?|"+PARAM_LIST+"}{-join|&|"+PARAM_LIST+"}")
				.setTemplate(TargetType.TYPE_CATEGORIES, "{target_base}/{collection};categories")
				.setTemplate(TargetType.TYPE_ENTRY, "{target_base}/{collection}({entry})")
				.setTemplate(AbstractCorrectedWorkspaceProvider.TYPE_ENTRIES_LIST, "{target_base}/{collection}({entries})")
				.setTemplate(AbstractCorrectedWorkspaceProvider.TYPE_ENTRY_LINKS, "{target_base}/{collection}({entry})/$links/{navigation}{-opt|?|"+PARAM_LIST+"}{-join|&|"+PARAM_LIST+"}")
				.setTemplate(OpenSearchTargetTypes.OPENSEARCH_DESCRIPTION, "{target_base}/opensearch.osdx")
				.setTemplate(OpenSearchTargetTypes.OPENSEARCH_URL, "{target_base}/{collection}{target_entry}{-opt|?|"+PARAM_LIST+"}{-join|&|"+PARAM_LIST+"}")
		);
        
        SimpleOpenSearchInfo openSearchInfo = new SimpleOpenSearchInfo();
		openSearchInfo.setShortName("OpenSearch");
		openSearchInfo.setDescription("Search in "+wi.getTitle(null));
		openSearchInfo.setTags(wi.getTitle(null));
//				.setContact("john.doe@example.org")
//		openSearchInfo.setTemplate(host+base+"SEARCH?q={searchTerms}&c={count?}&s={startIndex?}&p={startPage?}&l={language?}&i={indexEncoding?}&o={outputEncoding?}");

		SimpleOpenSearchQueryInfo openSearchQueryInfo = new SimpleOpenSearchQueryInfo();
		openSearchQueryInfo.setRole(Query.Role.REQUEST);
		openSearchInfo.setQueries(openSearchQueryInfo);
		
		// PARAM_SEARCHTERMS+','+PARAM_STARTINDEX+','+PARAM_FILTER+','+PARAM_SKIP+','+PARAM_TOP+','+PARAM_SORT;
		SimpleOpenSearchUrlParameterInfo searchTerm = new SimpleOpenSearchUrlParameterInfo();
		searchTerm.setName(PARAM_SEARCHTERMS);
		searchTerm.setValue(OpenSearchConstants.QUERY_SEARCHTERMS_LN);
		searchTerm.setOptional(true);

		SimpleOpenSearchUrlParameterInfo startIndex = new SimpleOpenSearchUrlParameterInfo();
		startIndex.setName(PARAM_STARTINDEX);
		startIndex.setValue(OpenSearchConstants.QUERY_STARTINDEX_LN);
		startIndex.setOptional(true);
/*
		SimpleOpenSearchUrlParameterInfo startPage = new SimpleOpenSearchUrlParameterInfo();
		startPage.setName("p");
		startPage.setValue(OpenSearchConstants.QUERY_STARTPAGE_LN);
		startPage.setOptional(true);
*/
		SimpleOpenSearchUrlParameterInfo count = new SimpleOpenSearchUrlParameterInfo();
		count.setName(PARAM_COUNT);
		count.setValue(OpenSearchConstants.QUERY_COUNT_LN);
		count.setOptional(true);
		
		SimpleOpenSearchUrlParameterInfo language = new SimpleOpenSearchUrlParameterInfo();
		language.setName(PARAM_LANGUAGE);
		language.setValue(QUERY_LANGUAGE_LN);
		language.setOptional(true);
		
		SimpleOpenSearchUrlParameterInfo filter = new SimpleOpenSearchUrlParameterInfo();
		filter.setName(PARAM_FILTER);
		filter.setValue(QUERY_FILTER_LN);
		filter.setOptional(true);

		SimpleOpenSearchUrlParameterInfo skip = new SimpleOpenSearchUrlParameterInfo();
		skip.setName(PARAM_SKIP);
		skip.setValue(QUERY_SKIP_LN);
		skip.setOptional(true);

		SimpleOpenSearchUrlParameterInfo top = new SimpleOpenSearchUrlParameterInfo();
		top.setName(PARAM_TOP);
		top.setValue(OpenSearchConstants.QUERY_COUNT_LN);
		top.setOptional(true);
		
		SimpleOpenSearchUrlParameterInfo orderBy = new SimpleOpenSearchUrlParameterInfo();
		orderBy.setName(PARAM_SORT);
		orderBy.setValue(QUERY_SORT_LN);
		orderBy.setOptional(true);

		SimpleOpenSearchUrlInfo openSearchUrlInfo = new SimpleOpenSearchUrlInfo();
	    openSearchUrlInfo.setSearchPath('/'+base+"/");
		SimpleOpenSearchUrlInfo openSearchUrlCollectionInfo = new SimpleOpenSearchUrlInfo();
	    openSearchUrlCollectionInfo.setSearchPath('/'+base+"/{"+BasicProvider.PARAM_FEED+"}");
		SimpleOpenSearchUrlInfo openSearchUrlEntryInfo = new SimpleOpenSearchUrlInfo();
	    openSearchUrlEntryInfo.setSearchPath('/'+base+"/{"+BasicProvider.PARAM_FEED+"}({"+BasicProvider.PARAM_ENTRY+"})/$links/{"+AbstractCorrectedWorkspaceProvider.PARAM_NAVIGATION+"}");

	    /*
<OpenSearchDescription>
<ShortName>RPA Real Estate Search</ShortName>
<Description>Search Real Property Associates for listings by supplying the city, neighborhood, school district, zip code, MLS area id or the MLS listing id</Description>
<Url type="text/html" template="http://www.seattlehouses.com/Feeds/OpenSearchHTML.ashx?Query={searchTerms}"/>
<Url type="application/x-suggestions+json" template="http://www.seattlehouses.com/Feeds/OpenSearchJSON.ashx?Query={searchTerms}"/>
<Url type="application/x-suggestions+xml" template="http://www.seattlehouses.com/Feeds/OpenSearchXML.ashx?Query={searchTerms}"/>
<Image height="16" width="16" type="image/icon">http://www.seattlehouses.com/favicon.ico</Image>
</OpenSearchDescription>
	     */
        OpenSearchUrlAdapter openSearchUrlAdapter = this.newSearchUrlAdapter();
		openSearchUrlInfo.setOpenSearchUrlAdapter(openSearchUrlAdapter);
		openSearchUrlInfo.setSearchParameters(searchTerm,startIndex,count,language,filter,skip,top,orderBy);

		openSearchUrlCollectionInfo.setOpenSearchUrlAdapter(openSearchUrlAdapter);
		openSearchUrlCollectionInfo.setSearchParameters(searchTerm,startIndex,count,language,filter,skip,top,orderBy);

		openSearchUrlEntryInfo.setOpenSearchUrlAdapter(openSearchUrlAdapter);
		openSearchUrlEntryInfo.setSearchParameters(searchTerm,startIndex,count,language,filter,skip,top,orderBy);

		openSearchInfo.setUrls(openSearchUrlInfo,openSearchUrlCollectionInfo,openSearchUrlEntryInfo);

		SimpleOpenSearchQueryInfo exampleQuery = new SimpleOpenSearchQueryInfo();
		exampleQuery.setRole(Query.Role.EXAMPLE);
		exampleQuery.setSearchTerms("*string");
		openSearchInfo.setQueries(exampleQuery);
/*
 * <?xml version="1.0" encoding="UTF-8"?>
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
   <ShortName>Web Search</ShortName>
   <Description>Use Example.com to search the Web.</Description>
   <Tags>example web</Tags>
EXTENSION:<Contact>admin@example.com</Contact>
   <Url type="application/atom+xml"
        template="http://example.com/?q={searchTerms}&amp;pw={startPage?}&amp;format=atom"/>
CAUTION:{startIndex} is starting at 1 and not 0 and is therefore not compatible with OData $skip
CAUTION:{searchTerms} is not grammatically compatible with $filter
MISSING:<Url type="text/html" 
        template="http://example.com/?q={searchTerms}&amp;pw={startPage?}"/>
EXTENSION:<LongName>Example.com Web Search</LongName>
MISSING:<Image height="64" width="64" type="image/png">http://example.com/websearch.png</Image>
MISSING:<Image height="16" width="16" type="image/vnd.microsoft.icon">http://example.com/websearch.ico</Image>
POSSIBLE AND RECOMMENDED:<Query role="example" searchTerms="cat" />
EXTENSION:<Developer>Example.com Development Team</Developer>
EXTENSION:<Attribution>
     Search data Copyright 2005, Example.com, Inc., All Rights Reserved
   </Attribution>
EXTENSION:<SyndicationRight>open</SyndicationRight>
EXTENSION:<AdultContent>false</AdultContent>
EXTENSION:<Language>en-us</Language>
EXTENSION:<OutputEncoding>UTF-8</OutputEncoding>
EXTENSION:<InputEncoding>UTF-8</InputEncoding>
 </OpenSearchDescription>

 */
		OpenSearchDescriptionRequestProcessor openSearchDescriptionRequestProcessor = new OpenSearchDescriptionRequestProcessor();
		openSearchDescriptionRequestProcessor.setOpenSearchInfo(openSearchInfo);
		MultiCollectionOpenSearchUrlRequestProcessor openSearchUrlRequestProcessor = new MultiCollectionOpenSearchUrlRequestProcessor();
		openSearchUrlRequestProcessor.setOpenSearchInfo(openSearchInfo);
		
		EntryLinksRequestProcessor entryLinksRequestProcessor = new EntryLinksRequestProcessor();
		EntriesListRequestProcessor entriesListRequestProcessor = new EntriesListRequestProcessor();
		
		Map<TargetType,RequestProcessor> additionalProcessors = new HashMap<TargetType,RequestProcessor>();
		additionalProcessors.put(AbstractCorrectedWorkspaceProvider.TYPE_ENTRIES_LIST, entriesListRequestProcessor);
		additionalProcessors.put(AbstractCorrectedWorkspaceProvider.TYPE_ENTRY_LINKS, entryLinksRequestProcessor);
		additionalProcessors.put(OpenSearchTargetTypes.OPENSEARCH_DESCRIPTION, openSearchDescriptionRequestProcessor);
		additionalProcessors.put(OpenSearchTargetTypes.OPENSEARCH_URL,openSearchUrlRequestProcessor);
		addRequestProcessors(additionalProcessors);

		workspace = wi;
		addWorkspace(workspace);
/*
		addFilter(
				new OpenSearchFilter()
				.setShortName("OpenSearch")
				.setDescription("Search in "+wi.getTitle(null))
				.setTags(wi.getTitle(null))
//				.setContact("john.doe@example.org")
				.setTemplate(host+base+"SEARCH?q={searchTerms}&c={count?}&s={startIndex?}&p={startPage?}&l={language?}&i={indexEncoding?}&o={outputEncoding?}")
				.mapTargetParameter("q", "searchTerms")
				.mapTargetParameter("c", "count")
				.mapTargetParameter("s", "startIndex")
				.mapTargetParameter("p", "startPage")
				.mapTargetParameter("l", "language")
				.mapTargetParameter("i", "inputEncoding")
				.mapTargetParameter("o", "outputEncoding")
				);
*/
		addFilter(new JSONFilter());
	}

    @Override
    public String urlFor(RequestContext request, Object key, Object param) {
    	if (param != null && param instanceof Map) {
    		// OData parameters: Add backslash escaping the "$" ! 
    		Map<String,String> parameters = (Map<String,String>)param;
    		if (parameters.get("$$$$") == null) {
    			LinkedList<String> badKeys = new LinkedList<String>();
    			for (String aKey : parameters.keySet()) {
    				if (aKey.charAt(0) == '$') {
    					badKeys.add(aKey);
    				}
    			}
    			for (String aKey : badKeys) {
    				parameters.put('\\'+aKey,parameters.get(aKey));
    				parameters.remove(aKey);
    			}
    			parameters.put("$$$$", "!");
    		}
    	}
    	String url = super.urlFor(request,key,param);
		// OData parameters: remove the backslash escaping the "$" ! 
    	if (url != null) {
    		url = url.replace("\\$", "$");
    	}
    	return url;
    }

    public abstract OpenSearchUrlAdapter newSearchUrlAdapter();
    
	public static String getCurrentUser(RequestContext req) {
		String currentUser = (String) req.getAttribute(RequestContext.Scope.REQUEST, AskosiProviderServlet.CURRENT_USER_ATTRIBUTE);
		if (currentUser == null) {
			Principal principal = req.getPrincipal();
			if (principal != null) {
				currentUser = principal.getName();
			}
		}
		return currentUser;
	}

	/**
	 * @return the contextPath
	 */
	public String getContextPath() {
		return contextPath;
	}
}
